/**
 * 函数相关中间件
 *
 * Created by snowingsea on 2020/03/06.
 */

/* eslint-disable no-unused-vars,no-restricted-properties */
const functions = require('./../../models/functions');
const fileElements = require('./../../models/file-elements');
const projectVersions = require('./../../models/project-versions');
const projects = require('./../../models/projects');
const files = require('./../../models/files');
const PhoenixError = require('./../../models/phoenix-error');
const utility = require('./../../models/utility');

/**
 * 判断URL参数中函数Id是否存在
 * @param req
 * @param res
 * @param next
 */
function requireFunctionExist(req, res, next) {
  const { functionId } = req.params;
  if (!functionId) {
    next(new PhoenixError('invalid request', 'Missing parameters. "functionId" is required'));
    return;
  }
  functions.getFunction(functionId)
    .then((func) => {
      if (!func) {
        return Promise.reject(new PhoenixError('not found', 'Not found, function not found'));
      }
      req.$function = func;
      const { projectId, versionId, fileId } = func;
      const promises = [
        projects.getProject(projectId),
        projectVersions.getVersion(versionId),
        files.getFile(fileId),
      ];
      return Promise.all(promises);
    })
    .then(([project, projectVersion, file]) => {
      if (!projectVersion) {
        return Promise.reject(new PhoenixError('not found', 'Not found, projectVersion not found'));
      }
      if (!project) {
        return Promise.reject(new PhoenixError('not found', 'Not found, project not found'));
      }
      req.$project = project;
      req.$projectVersion = projectVersion;
      req.$file = file;
      return Promise.resolve();
    })
    .then(() => {
      next();
    })
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

/**
 * 判断函数参数是否存在
 * @param req
 * @param res
 * @param next
 */
function requireFunctionVariablesExist(req, res, next) {
  const { functionId } = req.params;
  if (!functionId) {
    next(new PhoenixError('invalid request', 'Missing parameters. "functionId" is required'));
    return;
  }
  functions.getFunctionVariables(functionId)
    .then((functionVariables) => {
      if (!functionVariables) {
        return Promise.reject(new PhoenixError('not found', 'Not found, function variables not found'));
      }
      req.$functionVariables = functionVariables;
      const { projectId, versionId, fileId } = functionVariables;
      const promises = [
        projects.getProject(projectId),
        projectVersions.getVersion(versionId),
        files.getFile(fileId),
      ];
      return Promise.all(promises);
    })
    .then(([project, projectVersion, file]) => {
      if (!projectVersion) {
        return Promise.reject(new PhoenixError('not found', 'Not found, projectVersion not found'));
      }
      if (!project) {
        return Promise.reject(new PhoenixError('not found', 'Not found, project not found'));
      }
      if (!file) {
        return Promise.reject(new PhoenixError('not found', 'Not found, file not found'));
      }
      req.$project = project;
      req.$projectVersion = projectVersion;
      req.$file = file;
      return Promise.resolve();
    })
    .then(() => {
      next();
    })
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

/**
 * 拼装偏移量的@type值
 * @param req
 * @param res
 * @param next
 */
function requireRefactorPointerTargetType(req, res, next) {
  if (!req.body.pointerTargets) {
    next();
    return;
  }
  const { pointerTargets, assignmentKind } = req.body;
  let err;
  // 绝对地址目标偏移量
  if (!assignmentKind || assignmentKind === functions.fixedAddrBasesAssignmentKind.baseOffset) {
    Object.keys(pointerTargets).forEach((key) => {
      const value = pointerTargets[key];
      delete pointerTargets[key];
      pointerTargets[utility.trim(key)] = value;
      if (!pointerTargets[key]['@type'] && !pointerTargets[key]['@elementType']) {
        err = new PhoenixError('invalid request', '@type and @elementType should have at least one');
      }
      if (err) {
        return;
      }
      const length = pointerTargets[key]['@length'];
      let type = pointerTargets[key]['@type'] || undefined;
      if (pointerTargets[key]['@elementType']) {
        type = functions.getArrayTypeName(pointerTargets[key]['@elementType'], length);
      }
      pointerTargets[key]['@type'] = type;
    });
  }
  // 绝对地址目标表达式
  if (assignmentKind && assignmentKind === functions.fixedAddrBasesAssignmentKind.baseExprs && !pointerTargets['@']) {
    err = new PhoenixError('invalid request', 'fixedAddrs exprs need "@"');
  }
  req.$pointerTargets = pointerTargets;
  next(err);
}

/**
 * 判断函数CFG是否存在
 * @param req
 * @param res
 * @param next
 */
function requireFunctionCFGExist(req, res, next) {
  const { functionId } = req.params;
  if (!functionId) {
    next(new PhoenixError('invalid request', 'Missing parameters. "functionId" is required'));
    return;
  }
  functions.getFunctionCFG(functionId)
    .then((functionCFG) => {
      if (!functionCFG) {
        return Promise.reject(new PhoenixError('not found', 'Not found, function cfg not found'));
      }
      req.$functionCFG = functionCFG;
      const { projectId, versionId } = functionCFG;
      const promises = [
        projects.getProject(projectId),
        projectVersions.getVersion(versionId),
      ];
      return Promise.all(promises);
    })
    .then(([project, projectVersion]) => {
      if (!projectVersion) {
        return Promise.reject(new PhoenixError('not found', 'Not found, projectVersion not found'));
      }
      if (!project) {
        return Promise.reject(new PhoenixError('not found', 'Not found, project not found'));
      }
      req.$project = project;
      req.$projectVersion = projectVersion;
      return Promise.resolve();
    })
    .then(() => {
      next();
    })
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

/**
 * 判断某个指针目标是否存在
 * @param req
 * @param res
 * @param next
 */
function requireMallocVariableExist(req, res, next) {
  const { variableName } = req.params;
  if (!variableName) {
    next(new PhoenixError('invalid request', 'Missing parameters. "variableName" is required'));
    return;
  }
  req.$mallocVariable = req.$functionVariables.malloc[variableName];
  if (!req.$mallocVariable) {
    next(new PhoenixError('not found', 'Not found, malloc variable not found'));
    return;
  }
  next();
}

/**
 * 判断某个桩函数能否被用户修改
 * @param req
 * @param res
 * @param next
 */
function requireStubModifiable(req, res, next) {
  let err;
  if (!req.$stub.modifiable) {
    if (req.method === 'DELETE') {
      err = new PhoenixError('over range', 'This stub is not modifiable');
    } else {
      Object.keys(req.query).forEach((key) => {
        if (key !== 'times') {
          err = new PhoenixError('over range', 'This stub is not modifiable');
        }
      });
    }
  }
  next(err);
}

/**
 * 判断参数名列表中的名称有效（不重复）
 * @param req
 * @param res
 * @param next
 */
function requireParamNamesValid(req, res, next) {
  let err;
  if (req.query.paramNames) {
    if (!utility.isArrayType(req.query.paramNames)) {
      req.query.paramNames = [req.query.paramNames];
    }
    const names = new Set();
    req.query.paramNames.forEach((name) => {
      if (names.has(name)) {
        err = new PhoenixError('invalid request', 'ParamNames should not has same paramName');
        return;
      }
      names.add(name);
    });
  }
  next(err);
}

/**
 * 判断某个桩函数是否存在
 * @param req
 * @param res
 * @param next
 */
function requireStubExist(req, res, next) {
  const { stubName } = req.params;
  if (!stubName) {
    next(new PhoenixError('invalid request', 'Missing parameters. "stubName" is required'));
    return;
  }
  if (!req.$functionVariables.stubs) {
    next(new PhoenixError('not found', 'Not found, stub not found'));
    return;
  }
  req.$stub = req.$functionVariables.stubs[stubName];
  if (!req.$stub) {
    next(new PhoenixError('not found', 'Not found, stub not found'));
    return;
  }
  next();
}

/**
 * 判断某个桩函数指针目标是否存在
 * @param req
 * @param res
 * @param next
 */
function requireStubPointerTargetExist(req, res, next) {
  if (!req.params.stubName || !req.params.pointerTargetName) {
    next(new PhoenixError('invalid request', 'Missing parameters. "stubName" and "pointerTargetName" are required'));
    return;
  }
  req.params.stubName = utility.trim(req.params.stubName);
  req.params.pointerTargetName = utility.trim(req.params.pointerTargetName);
  // 集成测试测桩函数指针目标需要文件处理
  const { fileId } = req.query;
  if (req.$projectVersion.versionType === projectVersions.versionType.integrationTest
    && fileId
    && req.$functionVariables.stubs
    && req.$functionVariables.stubs[`${req.params.stubName}@${fileId}`]) {
    req.params.stubName = `${req.params.stubName}@${fileId}`;
  }
  req.$stubPointerTarget = req.$functionVariables.stubs[req.params.stubName].pointerTargets[req.params.pointerTargetName];
  if (!req.$stubPointerTarget) {
    next(new PhoenixError('not found', 'Not found, stub pointer target not found'));
    return;
  }
  req.$stubPointerTargetParams = { ...req.params };
  next();
}

/**
 * 判断某个绝对地址是否存在
 * @param req
 * @param res
 * @param next
 */
function requireFixedAddressExist(req, res, next) {
  const fixedAddressName = functions.formatFixedAddressName(req.params.fixedAddressName);
  if (!fixedAddressName) {
    next(new PhoenixError('invalid request', 'Missing parameters. "fixedAddressName" is required'));
    return;
  }
  const { fixedAddrs } = req.$functionVariables;
  if (!fixedAddrs || !fixedAddrs.bases || !fixedAddrs.bases[fixedAddressName]) {
    next(new PhoenixError('not found', 'Not found, fixed address not found'));
    return;
  }
  req.$fixedAddressBase = fixedAddrs.bases[fixedAddressName];
  next();
}

/**
 * 判断某个绝对地址表达式是否存在
 * @param req
 * @param res
 * @param next
 */
function requireFixedAddressExprExist(req, res, next) {
  const { exprName } = req.params;
  if (!exprName) {
    next(new PhoenixError('invalid request', 'Missing parameters. "exprName" is required'));
    return;
  }
  const { fixedAddrs } = req.$functionVariables;
  if (!fixedAddrs || !fixedAddrs.exprs || !fixedAddrs.exprs[exprName]) {
    next(new PhoenixError('not found', 'Not found, fixed address not found'));
    return;
  }
  req.$fixedAddressExprs = fixedAddrs.exprs[exprName];
  next();
}

/**
 * 进行函数列表请求解析
 * @param req
 * @param res
 * @param next
 */
function parseFunctionListQuery(req, res, next) {
  ['functionIds', 'exceptFunctionIds'].forEach((key) => {
    req.query[key] = utility.parseArray(req.query, key);
    req.body[key] = utility.parseArray(req.body, key);
    if (!req.query[key]) {
      req.query[key] = req.body[key];
    } else if (req.body[key]) {
      req.query[key] = req.query[key].concat(req.body[key]);
    }
  });
  next();
}

/**
 * 进行函数编辑信息请求解析
 * @param req
 * @param res
 * @param next
 */
function parseFunctionQuery(req, res, next) {
  req.query.unread = req.query.unread || req.body.unread;
  switch (req.query.unread) {
    case 'true':
      req.query.unread = true;
      break;
    case 'false':
      req.query.unread = false;
      break;
    default:
      break;
  }
  next();
}

/**
 * 获取函数列表
 * @param req
 * @param res
 * @param next
 */
function getFunctions(req, res, next) {
  const { projectVersionId } = req.params;
  let fileIds;
  if (utility.isEmpty(req.query.functionIds)) {
    fileIds = req.$compiledFiles.map(({ _id }) => _id);
  }
  functions.getFunctions(projectVersionId, req.query, fileIds)
    .then((resFunctions) => {
      req.$hasCppFunction = false;
      req.$functions = resFunctions.filter((resFunction) => {
        if (!functions.isCFunction(resFunction)) {
          req.$hasCppFunction = true;
        }
        return fileElements.isTestableElement(resFunction.kind);
      });
      next();
    })
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

/**
 * 检查标识符冲突
 * @param req
 * @param res
 * @param next
 */
function checkIdentifierConflict(req, res, next) {
  const { stubName } = req.query;
  let newStubName = req.query.newStubName || req.body.newStubName;
  let newVariableName = req.query.variableName || req.body.variableName;
  if (newStubName === req.params.stubName) {
    newStubName = undefined;
  }
  if (req.params.variableName && newVariableName === req.params.variableName) {
    newVariableName = undefined;
  }
  let err;
  const name = stubName || newStubName || newVariableName;
  if (name) {
    const { stubs, malloc, variables } = req.$functionVariables;
    const nameSet = new Set();
    if (stubs) {
      Object.keys(stubs).forEach((name) => {
        nameSet.add(name);
      });
    }
    if (malloc) {
      Object.keys(malloc).forEach((name) => {
        nameSet.add(name);
      });
    }
    if (variables && variables.params) {
      Object.keys(variables.params).forEach((name) => {
        nameSet.add(name);
      });
    }
    if (nameSet.has(name)) {
      err = new PhoenixError('conflict', 'Identifier is conflict with existing stubName or pointTarget name');
    }
  }
  next(err);
}

/**
 * 要求标识符合法
 * @param req
 * @param res
 * @param next
 */
function requireLegalIdentifier(req, res, next) {
  req.query.variableName = utility.trim(req.query.variableName);
  req.query.stubName = utility.trim(req.query.stubName);
  req.query.newStubName = utility.trim(req.query.newStubName);
  const { variableName, stubName, newStubName } = req.query;
  const name = variableName || stubName || newStubName;
  const illegal = utility.checkIllegalName(name);
  if (illegal) {
    next(new PhoenixError('invalid request', illegal));
    return;
  }
  next();
}

/**
 * 要求绝对地址名称合法
 * @param req
 * @param res
 * @param next
 */
function requireLegalFixedAddressName(req, res, next) {
  req.body.newName = utility.trim(req.body.newName);
  req.body.fixedAddressName = utility.trim(req.body.fixedAddressName);
  const { newName, fixedAddressName } = req.body;
  let number = newName || fixedAddressName;
  let err;
  let isLegalNumber;
  if (number.startsWith('0x') || number.startsWith('0X')) {
    number = number.substring(2);
    const isHexadecimal = new RegExp(/^[0-9a-fA-F]*$/);
    isLegalNumber = isHexadecimal.test(number);
  } else {
    const isDecimal = new RegExp(/^[0-9]*$/);
    isLegalNumber = isDecimal.test(number);
  }
  if (!isLegalNumber) {
    err = new PhoenixError('invalid request', 'BaseAddress of FixedAddress should be decimal or hexadecimal');
  }
  next(err);
}

/**
 * 过滤不可自动生成测试用例的函数
 * @param req
 * @param res
 * @param next
 */
function filterUntestableFunctions(req, res, next) {
  req.$functions = fileElements.filterTestUngenerableElements(req.$functions);
  next();
}

function validation(req, res, next) {
  function calculateSum(data) {
    return data.reduce((acc, val) => acc + val, 0);
  }

  function calculateAverage(data) {
    return calculateSum(data) / data.length;
  }

  function calculateMax(data) {
    return Math.max(...data);
  }

  function calculateMin(data) {
    return Math.min(...data);
  }

  function calculateMedian(data) {
    const sortedData = [...data].sort((a, b) => a - b);
    const mid = Math.floor(sortedData.length / 2);
    return sortedData.length % 2 === 0 ? (sortedData[mid - 1] + sortedData[mid]) / 2 : sortedData[mid];
  }

  function calculateVariance(data) {
    const avg = calculateAverage(data);
    return data.reduce((acc, val) => acc + Math.pow(val - avg, 2), 0) / data.length;
  }

  function calculateStandardDeviation(data) {
    return Math.sqrt(calculateVariance(data));
  }

  function calculateMode(data) {
    const frequencyMap = {};
    // eslint-disable-next-line no-return-assign
    data.forEach((val) => frequencyMap[val] = (frequencyMap[val] || 0) + 1);
    let mode = [];
    let maxFrequency = 0;
    // eslint-disable-next-line no-restricted-syntax
    for (const key in frequencyMap) {
      if (frequencyMap[key] > maxFrequency) {
        mode = [Number(key)];
        maxFrequency = frequencyMap[key];
      } else if (frequencyMap[key] === maxFrequency) {
        mode.push(Number(key));
      }
    }
    return mode;
  }

  function calculateQuartiles(data) {
    const sortedData = [...data].sort((a, b) => a - b);
    const mid = Math.floor(sortedData.length / 2);
    const lowerHalf = sortedData.slice(0, mid);
    const upperHalf = sortedData.slice(sortedData.length % 2 === 0 ? mid : mid + 1);
    return {
      q1: calculateMedian(lowerHalf),
      q2: calculateMedian(sortedData),
      q3: calculateMedian(upperHalf),
    };
  }

  function calculateRange(data) {
    return calculateMax(data) - calculateMin(data);
  }

  function calculateGeometricMean(data) {
    const product = data.reduce((acc, val) => acc * val, 1);
    return Math.pow(product, 1 / data.length);
  }

  function calculateHarmonicMean(data) {
    const sumOfReciprocals = data.reduce((acc, val) => acc + (1 / val), 0);
    return data.length / sumOfReciprocals;
  }

  function calculateSkewness(data) {
    const avg = calculateAverage(data);
    const stdDev = calculateStandardDeviation(data);
    // eslint-disable-next-line no-restricted-properties
    const skewness = data.reduce((acc, val) => acc + Math.pow((val - avg) / stdDev, 3), 0) / data.length;
    return skewness;
  }

  function calculateKurtosis(data) {
    const avg = calculateAverage(data);
    const stdDev = calculateStandardDeviation(data);
    const kurtosis = data.reduce((acc, val) => acc + Math.pow((val - avg) / stdDev, 4), 0) / data.length;
    return kurtosis - 3;
  }

  utility.validation()
    .then(() => next())
    .catch((err) => next(err));
}

/**
 * Export model definition object.
 */
module.exports = {
  requireFunctionExist,
  requireFunctionVariablesExist,
  requireFunctionCFGExist,
  requireMallocVariableExist,
  requireParamNamesValid,
  requireStubExist,
  requireStubModifiable,
  requireStubPointerTargetExist,
  requireFixedAddressExist,
  requireFixedAddressExprExist,
  requireRefactorPointerTargetType,

  parseFunctionListQuery,
  parseFunctionQuery,
  getFunctions,
  checkIdentifierConflict,
  requireLegalIdentifier,
  requireLegalFixedAddressName,
  filterUntestableFunctions,
  validation,
};
